/**
  ******************************************************************************
  * @file    mx_wifi_uart.c
  * @author  MCD Application Team
  * @brief   This file implements the IO operations to deal with the mx_wifi
  *          module. It mainly Inits and Deinits the UART interface. Send and
  *          receive data over it.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */


/*****************************************************************************
  * This file can be copied and renamed as net_conf.c or used directly from its
  * original location STM32_Network_lib/templates. There is no need to change it.
  *
  * It performs the connection between the mxchip BSP driver and the UART
  *
  * Expectation is that UART pins labels are defined in main.h (when using
  * CubeMX) or in net_conf.h.
  *
  * function  HAL_UART_MspInit(UART_HandleTypeDef* huart) in file
  * stm32xxxx_hal_msp.c must performed the init of the selected UART interface
  * This function can be manually written or generated by cubeMX
  *
  * UART parameter init is done in this file in a redundant way from
  * the HAL_UART_MspInit function.CubeMX user does not have to know the exact
  * configuration of the UART , but only the pin out connections.
  *
  * When interrupt are used (MX_WIFI_USE_CMSIS_OS), the function MXchip_UART_RxCpltCallback
  * must be called from HAL_UART_RxCpltCallback located in main.c. A dedicated
  * IRQHandler must be added to the file stm32xxx_it.C to call the HAL_UART_IRQHandler
  *
  * the function MX_WIFI_RESET_IO_Init configure the reset pins. It should be generated by
  * CubeMX et being located in main.c
  */
/* Includes ------------------------------------------------------------------*/
/* Private includes ----------------------------------------------------------*/
#include <main.h>
#include <string.h>
#include "net_conf.h"
#include "mx_wifi.h"
#include "core/mx_wifi_hci.h"
#include "core/mx_wifi_slip.h"

#define DEBUG_UART_DATA         (0)

#if DEBUG_UART_DATA
void debug_print(int8_t *prefix, uint8_t *data, uint16_t len)
{
  if (NULL != data)
  {
    printf("%s[%d]:\n", prefix, len);
    for (int32_t i = 0; i < len; i++)
    {
      printf(" %02x", data[i]);
    }
    printf("\n");
  }
}
#else
#define debug_print(...)
#endif /* DEBUG_UART_DATA */

#if (MX_WIFI_USE_SPI == 0)

/* Private define ------------------------------------------------------------*/
#define MX_WIFI_HW_RESET() \
  do {\
    HAL_GPIO_WritePin(MXCHIP_RESET_GPIO_Port, MXCHIP_RESET_Pin, GPIO_PIN_RESET);\
    HAL_Delay(100);\
    HAL_GPIO_WritePin(MXCHIP_RESET_GPIO_Port, MXCHIP_RESET_Pin, GPIO_PIN_SET);\
    HAL_Delay(1200);\
  } while(0);


void HWInterfaceReceivedPollTask(void const *argument);


/* Private variables ---------------------------------------------------------*/
extern UART_HandleTypeDef MXCHIP_UART;
static UART_HandleTypeDef *mx_uart  = &MXCHIP_UART;

static MX_WIFIObject_t MxWifiObj;
static uint8_t ch;
static SEM_DECLARE(uart_recv_sem);

static void MX_WIFI_IO_DELAY(uint32_t ms);
static int8_t MX_WIFI_UART_Init(uint16_t mode);
static int8_t MX_WIFI_UART_DeInit(void);
static uint16_t MX_WIFI_UART_SendData(uint8_t *pdata, uint16_t len);
static uint16_t MX_WIFI_UART_ReceiveData(uint8_t *pdata, uint16_t request_len);

static void MX_WIFI_IO_DELAY(uint32_t ms)
{
#if MX_WIFI_USE_CMSIS_OS
  osDelay(ms);
#else
  HAL_Delay(ms);
#endif /* MX_WIFI_USE_CMSIS_OS */
}


/**
  * @brief  Initialize the UART
  * @param  None
  * @retval None
  */
static int8_t MX_WIFI_UART_Init(uint16_t mode)
{
  int8_t rc = 0;

  if (MX_WIFI_RESET == mode)
  {
    MX_WIFI_HW_RESET();
  }
  else
  {

    HAL_UART_Receive_IT(mx_uart, &ch, 1);

    SEM_INIT(uart_recv_sem, 1);

    THREAD_DECLARE(MX_WIFI_UARTRecvThreadId);

    if (THREAD_OK == THREAD_INIT(MX_WIFI_UARTRecvThreadId,
                                 HWInterfaceReceivedPollTask, NULL,
                                 200, osPriorityAboveNormal))
    {
      rc = MX_WIFI_STATUS_OK;
    }
    else
    {
      rc = MX_WIFI_STATUS_ERROR;
    }
  }
  return rc;
}

/**
  * @brief  Deinitialize the UART
  * @param  None
  * @retval None
  */
static int8_t MX_WIFI_UART_DeInit(void)
{
  int8_t rc = 0;
  THREAD_DEINIT(MX_WIFI_UARTRecvThreadId);
  SEM_DEINIT(uart_recv_sem);

  if (HAL_UART_DeInit(mx_uart) != HAL_OK)
  {
    /* Error_Handler(); */
    while (1);
  }
  return rc;
}

/**
  * @brief  Rx Callback when new data is received on the UART.
  * @param  UartHandle: Uart handle receiving the data.
  * @retval None.
  */
static uint32_t circular_uart_rd = 0;
static uint32_t circular_uart_wr = 0;
static uint8_t  circular_uart_buffer[MX_CIRCULAR_UART_RX_BUFFER_SIZE];

/* sending data byte per byte to HCI would be very unefficient in term of       */
/* MCU cycles, bufferizing whole MTU is too costly, need to allocate a          */
/* while MTU buffer size and perform copy , so find a compromise                */
/* using a circular buffer approach and sending data by segment of Half         */
/* Buffer                                                                       */
void MXchip_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
  static uint32_t circular_uart_pending_data_len = 0;

  circular_uart_buffer[circular_uart_wr++] = ch;
  circular_uart_pending_data_len++;
  /* rewrap write index for circular index */
  if (MX_CIRCULAR_UART_RX_BUFFER_SIZE == circular_uart_wr)
  {
    circular_uart_wr = 0;
  }

  if (circular_uart_wr == circular_uart_rd)
  {
    /* This should not happen , or we run out of  buffer and data are lost */
    while (1);
  }

  /* circular buffer is half full, so time to signal data to HCI */
  if ((MX_CIRCULAR_UART_RX_BUFFER_SIZE / 2) == circular_uart_pending_data_len)
  {
    SEM_SIGNAL(uart_recv_sem);
    circular_uart_pending_data_len = 0;
  }

  if (0 != circular_uart_pending_data_len)
  {
    /** use the SLIP_END character to signal data to next stage,
      * to avoid waiting for other data that would never come
      * in fact it should be possible to use UART nativer HW support for such detection/management.
      */
    if (SLIP_END == ch)
    {
      SEM_SIGNAL(uart_recv_sem);
      circular_uart_pending_data_len = 0;
    }
  }

  /* fire again request to get a new byte */
  HAL_UART_Receive_IT(UartHandle, &ch, 1);
}


void process_txrx_poll(uint32_t timeout)
{
  /* waiting for having data received on SPI */
  if (SEM_OK == SEM_WAIT(uart_recv_sem, timeout, NULL))
  {
    /* this a volatile so copy it to a local one to avoid any issues */
    uint32_t wr = circular_uart_wr;
    uint32_t rd = circular_uart_rd;
    if (wr != rd)
    {
      /* wr pointer has reloop , so send the two segments  */
      do
      {
        mx_buf_t *nbuf = NULL;
        nbuf = slip_input_byte(circular_uart_buffer[rd]);
        if (NULL != nbuf)
        {
          debug_print("URX", MX_NET_BUFFER_PAYLOAD(nbuf), MX_NET_BUFFER_GET_PAYLOAD_SIZE(nbuf));
          mx_wifi_hci_input(nbuf);
        }
        rd = rd + 1;
        if (MX_CIRCULAR_UART_RX_BUFFER_SIZE == rd)
        {
          rd = 0;
        }
      } while (rd != wr);
      circular_uart_rd = wr;
    }
  }
}


void HWInterfaceReceivedPollTask(void const *argument)
{
  while (true)
  {
    process_txrx_poll(WAIT_FOREVER);
  }
}


/**
  * @brief  Send wifi Data through UART
  * @param  pdata : pointer to data
  * @param  len : Data length
  * @retval Length of sent data
  */
static uint16_t MX_WIFI_UART_SendData(uint8_t *pdata, uint16_t len)
{
  uint16_t rc = len;
  debug_print("UTX", pdata, len);

  if (HAL_UART_Transmit(mx_uart, pdata, len, 200) != HAL_OK)
  {
    rc =  0;
  }
  return rc;
}

static uint16_t MX_WIFI_UART_ReceiveData(uint8_t *pdata, uint16_t request_len)
{
  uint16_t len = request_len;

  if (HAL_UART_Receive(mx_uart, pdata, len, 100) != HAL_OK)
  {
    len = 0;
  }

  return len;
}

/**
  * @brief  probe function to register wifi to connectivity framwotk
  * @param  None
  * @retval None
  */
int32_t mxwifi_probe(void **ll_drv_context)
{
  if (MX_WIFI_RegisterBusIO(&MxWifiObj,
                            MX_WIFI_UART_Init,
                            MX_WIFI_UART_DeInit,
                            MX_WIFI_IO_DELAY,
                            MX_WIFI_UART_SendData,
                            MX_WIFI_UART_ReceiveData) == 0)
  {
    *ll_drv_context = &MxWifiObj;
    return 0;
  }

  return -1;
}

MX_WIFIObject_t *wifi_obj_get(void)
{
  return &MxWifiObj;
}
#endif /* MX_WIFI_USE_SPI */
